package fr.ups.jim.rdf.tp1;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.lang.Math;
import java.util.Collection;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;

public class DecisionNode {
    
    private String label;
    private String criteria;
    private Map<String, DecisionNode> children;
    private int nodeId;
    public static int NODE_ID = 0;

    public DecisionNode() {
    	this(null, null, new HashMap<String, DecisionNode>());
    }

    // constructor for a leaf
    public DecisionNode(String label) {
    	this(label, null, new HashMap<String, DecisionNode>());
    }

    // constructor for an internal node
    public DecisionNode(String criteria, Map<String, DecisionNode> children) {
    	this(null, criteria, children);
    }

    private DecisionNode(String label, String criteria, Map<String, DecisionNode> children) {
    	this.label = label;
    	this.criteria = criteria;
    	this.children = children;
    	this.nodeId = DecisionNode.NODE_ID;
    	DecisionNode.NODE_ID += 1;
    }

    public DecisionNode nextNode(Map<String, String> example) {
		String value = example.get(this.criteria);
		DecisionNode next = this.children.get(value);
		if (next == null) {
		    throw new RuntimeException("pas de descendant associe a la valeur " + value + " du critere " + this.criteria);
		}
		return next;
    }

    public String getLabel() {
    	return this.label;
    }
    
    public boolean isFinal() {
    	return this.label != null;
    }

    public void dot() {
    	this.dot(true);
    }

    
    private void dot(boolean header) {
		if (header) {
			System.out.println("digraph G {");
		}
		
	    String node_name = "node" + this.nodeId;
	    if (this.isFinal()) {
	    	System.out.println("\t" + node_name + "[label=" + this.label + "];");
		} 
	    else {
	    	System.out.println("\t" + node_name + "[label=" + this.criteria + ",shape=box];");
		}
	
		for (Map.Entry<String, DecisionNode> item : this.children.entrySet()) {
		    String criteria = item.getKey();
		    DecisionNode node = item.getValue();
		    System.out.println("\tnode" + this.nodeId + " -> node" + node.nodeId + "[label=" + criteria + "];");
	        node.dot(false);
		}
	    
		if (header) {
		    System.out.println("}");
		}	
    }

    // Method for Question 4 (Partie 1)
    // Input: 1. Map<String, String> example : an example described by an hashmap
    //		  2. DecisionNode dt : this desicion tree is manually constructed in Question 3
    // Output: based on the decision_tree, return a label result "yes" or "no" for the given example
    public static String classify(DecisionNode dt, Map<String, String> example) {
		DecisionNode current = dt;
		while (!current.isFinal()) {
		    current = current.nextNode(example);
		}
		return current.label;
    }   
    
    public static void main(String[] a) throws IOException, FileNotFoundException {
//		ArrayList<Map<String, String>> data = ID3.readData("tennis.dat");

/*************************************************
//		For Question 2
		for (int i=0; i < data.size(); i++)
			System.out.println(data.get(i));
**************************************************/


/*************************************************
//		For Question 3			
		Map<String, DecisionNode> map1 = new HashMap<String, DecisionNode>();
		DecisionNode leaf1 = new DecisionNode("no");
		DecisionNode leaf2 = new DecisionNode("yes");
		map1.put("high", leaf1);
		map1.put("normal", leaf2);
		DecisionNode node_humidity = new DecisionNode("humidity", map1);
		
		Map<String, DecisionNode> map2 = new HashMap<String, DecisionNode>();
		DecisionNode leaf3 = new DecisionNode("no");
		DecisionNode leaf4 = new DecisionNode("yes");
		map2.put("strong", leaf3);
		map2.put("weak", leaf4);
		DecisionNode node_wind = new DecisionNode("wind", map2);
		
		Map<String, DecisionNode> map3 = new HashMap<String, DecisionNode>();
		DecisionNode leaf5 = new DecisionNode("yes");
		map3.put("sunny", node_humidity);
		map3.put("rain", leaf5);
		map3.put("overcast", node_wind);
		DecisionNode node_outlook = new DecisionNode("outlook", map3);
		
//		node_outlook.dot();
**************************************************/
		

/***************************************************
//		Test for question 4 (Test for question 3 is prerequisite)
		for(Map<String, String> example : data) 
			System.out.println(DecisionNode.classify(node_outlook, example));
****************************************************/
		
//		ArrayList<String> labels = new ArrayList<String>();
//		//labels.add("playtennis");
//		labels.add("outlook");
//		labels.add("temperature");
//		labels.add("humidity");
//		labels.add("wind");
//		    
//		DecisionNode n = ID3.id3(data, "playtennis", labels);
		//n.dot();
//		System.out.println(ID3.calculateError(n, "tennis.dat"));
		
		// double entropy = ID3.entropy(data, "playtennis");
//		String chosen_attr = ID3.chooseAttribute(data, labels, "playtennis");
//		System.out.println(chosen_attr);
		// System.out.println(gain_humidity);
    	
/***************************************************************************
//		Test for question 11 
    	String fileName = "newsgroup.dat";
    	Set<String> vocabulary = ID3.calculateVocabulary(fileName);
    	
    	// because the vocabulary size is too big to output in the console
    	// I try to write the result in a .txt file
    	FileWriter fw = new FileWriter("c:/result.txt");
        BufferedWriter bw = new BufferedWriter(fw);   
    	
        int i = 0;	
        ArrayList<String> line = new ArrayList<String>();
        for (String word : vocabulary){
    		line.add(word);
    		i++;
    		// control every can only contain 10 words
    		if (i % 10 == 0){
    			bw.write(line.toString());
    			bw.newLine();
    			line.clear();
    			i = 0;
    		}	
    	}
        
        // Perhaps, the last line is not long enough(contains less than 10 words) to output
        if (i != 0)
        	bw.write(line.toString());
        
        bw.flush();
        bw.close();
        fw.close();
***************************************************************************/

    	// Test for question 13
    	
    	ArrayList<Map<String, String>> data = new ArrayList<Map<String,String>>();
    	data = ID3.readNewsgroup("newsgroup_for_Q13.dat");
    	
    	Set<String> attributes = ID3.calculateVocabulary("newsgroup_for_Q13.dat");
    	ArrayList<String> labels = new ArrayList<String>();
    	for (String label : attributes) {
    		labels.add(label);
    	}
    	DecisionNode n = ID3.id3LimitSize(data, "ForumName", labels,5);
    	
    	System.out.println(ID3.calculateErrorLimitSize(n, "newsgroup_for_Q13.dat"));
    	

    	
    }
}
